/*******************************************************************************
 *
 *      Filename: test_vec.cpp
 *   Description: Unit tests for vector math
 *        Author: [kb]ypp
 *    Created at: December 13, 2006
 * Last modified: 
 *
 ******************************************************************************/

#include "../VectorMath.h"
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
using namespace boost::unit_test;
using boost::test_toolbox::close_at_tolerance;
#include <cmath>
#include <iostream>

using namespace std;
using namespace math;

const float TOLERANCE = 0.0001;

template< typename T >
ostream& operator<<( ostream &os, const Vec2< T > &v )
{
	return os << "( " << v.x << "; " << v.y << " )";
}

template< typename T >
ostream& operator<<( ostream &os, const Vec3< T > &v )
{
	return os << "( " << v.x << "; " << v.y << "; " << v.z << " )";
}

template< typename T >
ostream& operator<<( ostream &os, const Vec4< T > &v )
{
	return os << "( " << v.x << "; " << v.y << "; " << v.z << "; " << v.w << " )";
}

void constructors_test()
{
	// Use the default constructor:
	float3 df;
	
	// Test the constructor with 3 float`s:
	float3 v( -1.0, 0.0, 1.0 );
	BOOST_CHECK_CLOSE( v.x, -1.0f, TOLERANCE );
	BOOST_CHECK_CLOSE( v.y, 0.0f, TOLERANCE );
	BOOST_CHECK_CLOSE( v.z, 1.0f, TOLERANCE );
	
	// Test the copy-constructor:
	float3 vCopy( v );
	BOOST_CHECK_CLOSE( vCopy.x, v.x, TOLERANCE );
	BOOST_CHECK_CLOSE( vCopy.y, v.y, TOLERANCE );
	BOOST_CHECK_CLOSE( vCopy.z, v.z, TOLERANCE );
	float3 dfCopy( df );
	BOOST_CHECK_CLOSE( dfCopy.x, df.x, TOLERANCE );
	BOOST_CHECK_CLOSE( dfCopy.y, df.y, TOLERANCE );
	BOOST_CHECK_CLOSE( dfCopy.z, df.z, TOLERANCE );
}

void assignment_test()
{
	// Test operator=()
	const float3 v( -1.0, 0.0, 1.0 );
	float3 vAssign;
	vAssign = v;
	BOOST_CHECK_CLOSE( vAssign.x, v.x, TOLERANCE );
	BOOST_CHECK_CLOSE( vAssign.y, v.y, TOLERANCE );
	BOOST_CHECK_CLOSE( vAssign.z, v.z, TOLERANCE );
	
	const float3 v1( 1.0, 2.123, 4.5 );
	const float3 v2( 0.0, -0.56, 1.25 );
	
	// Test operator+=()
	float3 v12;
	v12 = v1;
	v12 += v2;
	BOOST_CHECK_CLOSE( v12.x, v1.x + v2.x, TOLERANCE );
	BOOST_CHECK_CLOSE( v12.y, v1.y + v2.y, TOLERANCE );
	BOOST_CHECK_CLOSE( v12.z, v1.z + v2.z, TOLERANCE );

	// Test operator-=()
	float3 v21;
	v21 = v2;
	v21 -= v1;
	BOOST_CHECK_CLOSE( v21.x, v2.x - v1.x, TOLERANCE );
	BOOST_CHECK_CLOSE( v21.y, v2.y - v1.y, TOLERANCE );
	BOOST_CHECK_CLOSE( v21.z, v2.z - v1.z, TOLERANCE );
}

void arithmetic_test()
{
	const float3 v( -1.0, 0.0, 1.0 );
	const float3 v1( 1.0, 2.123, 4.5 );
	const float3 v2( 0.0, -0.56, 1.25 );
	
	// Test unary operator-()
	BOOST_CHECK_CLOSE( ( -v ).x, -v.x, TOLERANCE );
	BOOST_CHECK_CLOSE( ( -v ).y, -v.y, TOLERANCE );
	BOOST_CHECK_CLOSE( ( -v ).z, -v.z, TOLERANCE );
	
	// Test operator+()
	BOOST_CHECK_CLOSE( ( v1 + v2 ).x, v1.x + v2.x, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v1 + v2 ).y, v1.y + v2.y, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v1 + v2 ).z, v1.z + v2.z, TOLERANCE );
	
	// Test operator-()
	BOOST_CHECK_CLOSE( ( v1 - v2 ).x, v1.x - v2.x, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v1 - v2 ).y, v1.y - v2.y, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v1 - v2 ).z, v1.z - v2.z, TOLERANCE );
	
	// Test operator*()
	const float a = -0.123;
	BOOST_CHECK_CLOSE( ( a * v ).x, a * v.x, TOLERANCE );
	BOOST_CHECK_CLOSE( ( a * v ).y, a * v.y, TOLERANCE );
	BOOST_CHECK_CLOSE( ( a * v ).z, a * v.z, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v * a ).x, a * v.x, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v * a ).y, a * v.y, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v * a ).z, a * v.z, TOLERANCE );
	const float b = 789.321;
	BOOST_CHECK_CLOSE( ( b * v ).x, b * v.x, TOLERANCE );
	BOOST_CHECK_CLOSE( ( b * v ).y, b * v.y, TOLERANCE );
	BOOST_CHECK_CLOSE( ( b * v ).z, b * v.z, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v * b ).x, b * v.x, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v * b ).y, b * v.y, TOLERANCE );
	BOOST_CHECK_CLOSE( ( v * b ).z, b * v.z, TOLERANCE );
}

void comparison_test()
{
	float3 v1( -1.0, 0.0, 1.0 );
	float3 v2( -1.0, 0.0, 1.0 );
	
	// Test operator==()
	BOOST_CHECK_EQUAL( v1, float3( -1.0, 0.0, 1.0 ) );
	BOOST_CHECK_EQUAL( float3( -1.0, 0.0, 1.0 ), v1 );
	BOOST_CHECK_EQUAL( v1, v2 );
	
	v1.z = 2.0;
	
	// Test operator!=()
	BOOST_CHECK( v1 != float3( -1.0, 0.0, 1.0 ) );
	BOOST_CHECK( float3( -1.0, 0.0, 1.0 ) != v1 );
	BOOST_CHECK( v1 != v2 );
	
	BOOST_CHECK_EQUAL( v1, float3( -1.0, 0.0, 2.0 ) );
}

void other_test()
{
	const float3 v( -1.0, 0.0, 1.0 );
	const float3 v1( 1.0, 2.123, 4.5 );
	const float3 v2( 0.0, -0.56, 1.25 );
	
	// Test normalize()
	float3 unit_v;
	unit_v = v;
	normalize( unit_v );
	BOOST_CHECK_CLOSE( length( unit_v ), 1.0f, TOLERANCE );
	unit_v = v1;
	normalize( unit_v );
	BOOST_CHECK_CLOSE( length( unit_v ), 1.0f, TOLERANCE );
	unit_v = v2;
	normalize( unit_v );
	BOOST_CHECK_CLOSE( length( unit_v ), 1.0f, TOLERANCE );
	normalize( float3( float3::ZERO ) ); // Log warning message expected!
	
	// Test length()
	BOOST_CHECK_CLOSE( length(  v ), 1.414213562373095f, TOLERANCE );
	BOOST_CHECK_CLOSE( length( v1 ), 5.07514817517676f, TOLERANCE );
	BOOST_CHECK_CLOSE( length( v2 ), 1.369707998078423f, TOLERANCE );
	
	// Test lengthSq()
	BOOST_CHECK_CLOSE( lengthSq(  v ), 2.0f, TOLERANCE );
	BOOST_CHECK_CLOSE( lengthSq( v1 ), 25.757129f, TOLERANCE );
	BOOST_CHECK_CLOSE( lengthSq( v2 ), 1.8761f, TOLERANCE );
	
	BOOST_CHECK_CLOSE( length(  v ), sqrt( lengthSq(  v ) ), TOLERANCE );
	BOOST_CHECK_CLOSE( length( v1 ), sqrt( lengthSq( v1 ) ), TOLERANCE );
	BOOST_CHECK_CLOSE( length( v2 ), sqrt( lengthSq( v2 ) ), TOLERANCE );
	
	const float3 i( 1.0, 0.0, 0.0 ),
			   j( 0.0, 1.0, 0.0 ),
			   k( 0.0, 0.0, 1.0 );
	
	// Test dot()
	BOOST_CHECK_CLOSE( dot( v1, v2 ), dot( v2, v1 ), TOLERANCE );
	BOOST_CHECK_CLOSE( dot( v1, v2 ), 4.43612f, TOLERANCE );
	BOOST_CHECK_CLOSE( dot( v, v1 ), 3.5f, TOLERANCE );
	BOOST_CHECK_CLOSE( dot( v1, v ), 3.5f, TOLERANCE );
	
	BOOST_CHECK_CLOSE( dot( i, j ), 0.0f, TOLERANCE );
	BOOST_CHECK_CLOSE( dot( j, i ), 0.0f, TOLERANCE );
	BOOST_CHECK_CLOSE( dot( j, k ), 0.0f, TOLERANCE );
	BOOST_CHECK_CLOSE( dot( k, j ), 0.0f, TOLERANCE );
	BOOST_CHECK_CLOSE( dot( k, i ), 0.0f, TOLERANCE );
	BOOST_CHECK_CLOSE( dot( i, k ), 0.0f, TOLERANCE );
	
	// Test cross()
	BOOST_CHECK_EQUAL( cross( v, v1 ), float3( -2.123,  5.5, -2.123 ) );
	BOOST_CHECK_EQUAL( cross( v1, v ), float3(  2.123, -5.5,  2.123 ) );
	BOOST_CHECK_EQUAL( cross( v, v2 ), float3(  0.56,  1.25,  0.56 ) );
	BOOST_CHECK_EQUAL( cross( v2, v ), float3( -0.56, -1.25, -0.56 ) );
	BOOST_CHECK_EQUAL( cross( v1, v2 ), -cross( v2, v1 ) );
	BOOST_CHECK( cross( v1, v2 ) != cross( v2, v1 ) );
	
	BOOST_CHECK_EQUAL( cross( i, j ),  k );
	BOOST_CHECK_EQUAL( cross( j, i ), -k );
	BOOST_CHECK_EQUAL( cross( j, k ),  i );
	BOOST_CHECK_EQUAL( cross( k, j ), -i );
	BOOST_CHECK_EQUAL( cross( k, i ),  j );
	BOOST_CHECK_EQUAL( cross( i, k ), -j );
}

test_suite* init_unit_test_suite( int argc, char* argv[] )
{
	test_suite *test = BOOST_TEST_SUITE( "Unit tests for float3 class" );
	
	test->add( BOOST_TEST_CASE( &constructors_test ) );
	test->add( BOOST_TEST_CASE( &assignment_test ) );
	test->add( BOOST_TEST_CASE( &arithmetic_test ) );
	test->add( BOOST_TEST_CASE( &comparison_test ) );
	test->add( BOOST_TEST_CASE( &other_test ) );
	
	return test;
}
